package com.gc.libnet

import android.content.Context
import android.util.Log
import com.gc.libnet.interceptor.CommonInterceptor
import com.gc.libnet.interceptor.HttpLoggingInterceptor
import com.google.gson.Gson
import okhttp3.Dns
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import org.apache.http.conn.ssl.AllowAllHostnameVerifier
import retrofit2.CallAdapter
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

class HttpManager {
    var logger: (String, String, tr: Throwable?, isDebug: Boolean) -> Unit =
        { _: String, content: String, throwable: Throwable?, isDebug: Boolean ->
            Log.println(if (isDebug) Log.DEBUG else Log.ERROR,
                Tag,
                content + ":" + Log.getStackTraceString(throwable))
        }
    lateinit var mContext: Context
    private var okHttpClient: OkHttpClient? = null
    private val apiCache: HashMap<String, HashMap<String, Any>> = HashMap()

    companion object {
        const val Tag = "HttpManager"
        private const val DEFAULT_CONNECT_TIME = 10L
        private const val DEFAULT_READ_TIME = 10L
        const val SUCCESS = 200
        const val UNAUTHORIZED = 401
        const val FORBIDDEN = 403
        const val NOT_FOUND = 404
        const val REQUEST_TIMEOUT = 408
        const val INTERNAL_SERVER_ERROR = 500
        const val BAD_GATEWAY = 502
        const val SERVICE_UNAVAILABLE = 503
        const val GATEWAY_TIMEOUT = 504

        const val UNKNOWN = 1000
        const val PARSE_ERROR = 1001
        const val UNKNOW_HOST = 1003
        const val SSL_ERROR = 3001


        @JvmStatic
        val INSTANCE by lazy { HttpManager() }

        @JvmStatic
        suspend fun <T : Any> callRequest(
            call: suspend () -> RestfulResponse<T>
        ): RestfulResponse<T> {
            return try {
                call()
            } catch (e: Exception) {
                DealException.handlerException(e)
            }
        }
        @JvmStatic
        fun <T : Any> callImmediate(
            call: () -> RestfulResponse<T>
        ): RestfulResponse<T> {
            return try {
                call()
            } catch (e: Exception) {
                DealException.handlerException(e)
            }
        }
    }

    @JvmOverloads
    fun log(content: String, tr: Throwable? = null, isDebug: Boolean = true) {
        logger(Tag, content, tr, isDebug)
    }

    fun init(
        context: Context,
        logger: (String, String, tr: Throwable?, isDebug: Boolean) -> Unit,
    ): HttpManager {
        this.mContext = context
        this.logger = logger
        return INSTANCE
    }

    private fun <T> create(clazz: Class<T>, apiBuilder: Builder): T {
        val url = apiBuilder.baseUrl
        var apiMap = apiCache[url]
        if (apiMap == null) {
            apiMap = java.util.HashMap()
            apiCache[url] = apiMap
        }
        val key = clazz.name
        var api = apiMap[key] as T?
        if (api == null) {
            synchronized(HttpManager::class.java) {
                if (api == null) {
                    if (okHttpClient == null) initOkHttp(apiBuilder)
                    val builder = Retrofit.Builder()
                        .client(okHttpClient)
                        .baseUrl(url)
                    if (apiBuilder.converterFactories.isEmpty()) {
                        builder.addConverterFactory(GsonConverterFactory.create(Gson()))
                    } else {
                        for (factory in apiBuilder.converterFactories) {
                            builder.addConverterFactory(factory)
                        }
                    }
                    for (factory in apiBuilder.callAdapterFactories) {
                        builder.addCallAdapterFactory(factory)
                    }
                    api = builder.build().create(clazz) as T
                    apiMap[key] = api!!
                }
            }
        }
        return api!!
    }

    private fun initOkHttp(apiBuilder: Builder) {
        val okHttpBuilder: OkHttpClient.Builder = OkHttpClient.Builder()
            .retryOnConnectionFailure(true)
            .readTimeout(apiBuilder.readTime, TimeUnit.SECONDS)
            .connectTimeout(apiBuilder.connectTime, TimeUnit.SECONDS)
        okHttpBuilder.addInterceptor(CommonInterceptor())
        apiBuilder.okHttpConfig?.let { it.config(okHttpBuilder) }
        val interceptors: List<Interceptor> = apiBuilder.interceptors
        for (interceptor in interceptors) {
            okHttpBuilder.addInterceptor(interceptor)
        }
        okHttpBuilder.addInterceptor(HttpLoggingInterceptor())
        okHttpBuilder.hostnameVerifier(AllowAllHostnameVerifier())
        apiBuilder.dns?.let { okHttpBuilder.dns(it) }
        okHttpClient = okHttpBuilder.build()
    }

    class Builder {
        var okHttpConfig: OkHttpConfig? = null
        var interceptors: ArrayList<Interceptor> = ArrayList()
        var converterFactories: ArrayList<Converter.Factory> = ArrayList()
        var callAdapterFactories: ArrayList<CallAdapter.Factory> = ArrayList()
        lateinit var baseUrl: String
        var dns: Dns? = null
        var connectTime: Long = DEFAULT_CONNECT_TIME
        var readTime: Long = DEFAULT_READ_TIME

        fun config(okHttpConfig: OkHttpConfig?): Builder {
            this.okHttpConfig = okHttpConfig
            return this
        }

        fun addConverterFactory(converterFactory: Converter.Factory): Builder {
            converterFactories.add(converterFactory)
            return this
        }

        fun addCallAdapterFactory(callAdapterFactory: CallAdapter.Factory): Builder {
            callAdapterFactories.add(callAdapterFactory)
            return this
        }

        fun addInterceptor(interceptor: Interceptor): Builder {
            interceptors.add(interceptor)
            return this
        }

        fun baseUrl(baseUrl: String): Builder {
            this.baseUrl = baseUrl
            return this
        }

        fun connectTimeOut(time: Long): Builder {
            this.connectTime = time
            return this
        }

        fun readTimeOut(time: Long): Builder {
            this.readTime = time
            return this
        }


        fun dns(dns: Dns?): Builder {
            this.dns = dns
            return this
        }

        fun <T> build(api: Class<T>): T {
            return INSTANCE.create(api, this)
        }
    }


}